#!/usr/bin/env python 
# -*- coding: utf-8 -*-  

import math

from point import Point
from circle import Circle
from line import Line
from section import Section
import sdxf

class Vectorizator:
    def __init__(self, _skeleton_arr, _width, _height):
        self.width = _width
        self.height = _height
        self.special_point_array = []
        self.usual_point_array = []
        self.line_array = []
        self.circle_array = []
        self.skeleton_arr = _skeleton_arr
        self.current_line, self.current_section = None, None
    
    def get_angle(self, _start_point, _end_point):
        deltax = float(_end_point.get_x() - _start_point.get_x())
        deltay = float(_end_point.get_y() - _start_point.get_y())
        angle = math.pi/2
        if deltax != 0:
            angle = math.atan(deltay*(-1)/deltax)
        return angle

#---------------------------------------------------------------------------------------
#ПОЛУЧЕНИЕ МАССИВА ТОЧЕК
#---------------------------------------------------------------------------------------    
    #получение количества соседей для точки
    def neighbors_count(self, i, j):
        neighbors_list = [self.skeleton_arr[i-1][j-1],self.skeleton_arr[i][j-1],self.skeleton_arr[i+1][j-1],
                          self.skeleton_arr[i+1][j],self.skeleton_arr[i+1][j+1],self.skeleton_arr[i][j+1],
                          self.skeleton_arr[i-1][j+1],self.skeleton_arr[i-1][j]]
        return sum(neighbors_list)
    
    #получение массива конечных точек
    def make_point_array(self):
        w,h = len(self.skeleton_arr)-2,len(self.skeleton_arr[0])-2
        for j in xrange(1,h):
            for i in xrange(1,w):
                if self.skeleton_arr[i][j] == 1:
                    neighbors = self.neighbors_count(i, j)
                    if neighbors != 2:
                        point = Point(i, j, neighbors)
                        self.special_point_array.append(point)
        for point in self.special_point_array:
            self.skeleton_arr[point.get_x()][point.get_y()] = 0

#---------------------------------------------------------------------------------------
#ВЕКТОРИЗАЦИЯ
#---------------------------------------------------------------------------------------       
    def vectorize(self):
        print "start"
        self.make_point_array()
        print "%d special points founded" % (len(self.special_point_array))
        #получение массива линий
        for point in self.special_point_array:
            if not point.get_checked():
                self.make_line_array(point)
        print "end line search"
        #разбивка линий на секции
        for line in self.line_array:
            self.make_section_array(line)
        #проверка линии на прямую линию
        for line in self.line_array:
            line = self.check_direct(line)
        #проверка линии на набор прямых линий
        for line in self.line_array:
            if line.get_type() != "direct":
                line = self.check_direct_array(line)
        #выделение штриховки
        self.check_hatch()
        #проверка линий окружность
        for line in self.line_array:
            if (line.get_type() != "direct" and line.get_type() != "direct_array" and line.get_type() != "hatch"):
                line = self.check_arc(line)
        #конкатинация линий
        for line in self.line_array:
            #if line.get_type() == "direct":
            self.get_middle_point(line)
        dxf = self.generate_dxf()
        #отрисовка (только ля наглядности))
        arr = self.skeleton_arr[:]
        for line in self.line_array:
            if line.get_type() != "direct_array":
                for section in line.get_section_array():
                    for point in section.get_point_array():
                        arr[point.get_x()][point.get_y()] = 3
                        if line.get_type() == "direct":
                            arr[point.get_x()][point.get_y()] = 4
                        if line.get_type() == "circle":
                            arr[point.get_x()][point.get_y()] = 5
                        if line.get_type() == "hatch":
                            arr[point.get_x()][point.get_y()] = 7
                    arr[section.get_start_point().get_x()][section.get_start_point().get_y()] = 2
                    arr[section.get_end_point().get_x()][section.get_end_point().get_y()] = 2
        for circle in self.circle_array:
            try:
                arr[int(circle.get_m_Center().get_x())][int(circle.get_m_Center().get_y())] = 6
            except:
                print "Центр окружности за пределами изображения"
                self.circle_array.remove(circle)
        return (arr, dxf)
    
    def check_hatch(self):
        hatch = []
        for line in self.line_array:
            if line.get_type() == "direct":
                a,b = line.get_direct_params()
                if a != 0:
                    angle = math.atan(abs(a))
                    if angle > 0.7 and angle < 0.86:
                        line.set_type("hatch")
                        hatch.append(line)
        maxR = math.sqrt(len(self.skeleton_arr)**2+len(self.skeleton_arr[0])**2)
        way_arr = [0]*int(maxR)             
        for line in hatch:
            a,b = line.get_direct_params()
            for direct in hatch:
                a1,b1 = direct.get_direct_params()
                way = int(math.ceil(abs(b-b1)*math.cos(math.atan((abs(a)+ abs(a1))/2)))/5)
                if way != 0:
                    way_arr[way] += 1
        index = max(way_arr)
        return index
    
    #проверка на окружность
    def check_arc(self, _line):
        len_point_array = len(_line.get_point_array())
        spec_point_array = len(_line.get_special_point_array())
        if (len_point_array != 0 and spec_point_array != 0):
            precent = (spec_point_array*100)/len_point_array
            if precent >= 13:
                circle = Circle()
                pt1 = _line.get_start_point()
                pt2 = _line.get_end_point()
                pt3 = _line.get_special_point_array()[spec_point_array/2]
                circle.make_circle(pt1, pt2, pt3)
                center_p = circle.get_m_Center()
                radius = circle.get_m_dRadius()
                total_error = self.check_circle_error(center_p, radius, _line)
                dif = total_error/spec_point_array
                if dif <= 2: #то есть среднее отклонение меньше 1
                    _line.set_type("circle")
                    self.circle_array.append(circle)
        return _line
    
    #оценки ошибки для метода наименьших квадратов    
    def check_circle_error(self, center, radius, line):
        total_error = 0
        for point in line.get_special_point_array():
            deltapoint = abs(math.sqrt((center.get_x()-point.get_x())**2 + (center.get_y()-point.get_y())**2)-radius)
            total_error += abs(deltapoint)
        return total_error
    
    #восстановление связности
    def get_middle_point(self, _line):
        x,y = _line.get_end_point().get_x(),_line.get_end_point().get_y()
        pnt = Point(x, y, 0)
        for line in self.line_array:
            line_end_x = line.get_end_point().get_x()
            line_end_y = line.get_end_point().get_y()
            line_start_x = line.get_start_point().get_x()
            line_start_y = line.get_start_point().get_y()
            if line.get_type() == "direct":
                if ((line_end_x == x-2 and line_end_y == y+2) or (line_end_x == x+2 and line_end_y == y+2) or (line_end_x == x and line_end_y == y+4)):
                    pnt = Point(x, y+2)
                    line.set_end_point(pnt)
                    _line.set_end_point(pnt)
                if ((line_start_x == x-2 and line_start_y == y+2) or (line_start_x == x+2 and line_start_y == y+2) or (line_start_x == x and line_start_y == y+4)):
                    pnt = Point(x, y+2)
                    line.set_start_point(pnt)
                    _line.set_end_point(pnt)
                if ((line_end_x == x and line_end_y == y-4) or (line_end_x == x-2 and line_end_y == y-2) or (line_end_x == x+2 and line_end_y == y-2)):
                    pnt = Point(x, y-2)
                    line.set_end_point(pnt)
                    _line.set_end_point(pnt)
                if ((line_start_x == x and line_start_y == y-4) or (line_start_x == x-2 and line_start_y == y-2) or (line_start_x == x+2 and line_start_y == y-2)):
                    pnt = Point(x, y-2)
                    line.set_start_point(pnt)
                    _line.set_end_point(pnt)

    #проверка линии на прямую линию методом наименьших квадратов
    def check_direct(self, line):
        total_error = self.check_direct_error(line)
        if total_error <= 2.7*(len(line.get_special_point_array()) - 2): #то есть среднее отклонение меньше 1
            line.set_type("direct")
        return line
    
    #оценки ошибки для метода наименьших квадратов    
    def check_direct_error(self, line):
        deltay = float((line.get_end_point().get_y() - line.get_start_point().get_y()))
        deltax = float((line.get_end_point().get_x() - line.get_start_point().get_x()))
        total_error = 0
        if deltax != 0:
            a = deltay/deltax
            b = line.get_start_point().get_y() - a*line.get_start_point().get_x()
            for point in line.get_special_point_array():
                deltapoint = point.get_y() - a*point.get_x() - b
                total_error += abs(math.cos(math.atan(a))*deltapoint)
        else:
            for point in line.get_special_point_array():
                total_error += abs(line.get_end_point().get_x() - point.get_x())
        return total_error
    
    #проверка линии на набор линий (прямых и кривых)
    def check_direct_array(self, line):
        turn_nums = []
        #пробегаем по всем секциям данной линии и ищем соседние секции отличающиеся на большой угол
        for i in xrange(len(line.get_section_array())-1):
            k1 = line.get_section_array()[i].get_tangent()
            k2 = line.get_section_array()[i+1].get_tangent()
            if abs(k1 - k2) > 1:
                turn_nums.append(i)  
        if len(turn_nums) > 0:
            turn_nums.append(len(line.get_section_array()) - 1)
            line.set_type("direct_array") 
            num = 0  
            for i in xrange(len(turn_nums)):
                new_line = Line(line.get_section_array()[num].get_start_point(), line.get_section_array()[turn_nums[i]].get_end_point())
                new_line.set_special_point_array(line.get_special_point_array()[num:turn_nums[i]+2])
                new_line.set_section_array(line.get_section_array()[num:turn_nums[i]+1])
                new_line = self.check_direct(new_line)
                self.line_array.append(new_line)
                num = turn_nums[i] + 1
        return line
    
    
    #Генерация DXF файла
    def generate_dxf(self):
        b=sdxf.Block('test')
        b.append(sdxf.Solid(points=[(0,0,0),(1,0,0),(1,1,0),(0,1,0)],color=1))
        b.append(sdxf.Arc(center=(1,0,0),color=2))
        #Drawing
        d=sdxf.Drawing()
        #tables
        d.blocks.append(b)                      #table blocks
        d.styles.append(sdxf.Style())                #table styles
        d.views.append(sdxf.View('Normal'))          #table view
        d.views.append(sdxf.ViewByWindow('Window',leftBottom=(1,0),rightTop=(2,1)))  #idem
        full_width = len(self.skeleton_arr)
        full_height = len(self.skeleton_arr[0])
        #entities
        for line in self.line_array:
            if line.get_type() == "direct" or line.get_type() == "hatch":
                xstart = float(line.get_start_point().get_x()*self.width/full_width)
                ystart = float((full_height - line.get_start_point().get_y())*self.height/full_height)
                xend = float(line.get_end_point().get_x()*self.width/full_width)
                yend = float((full_height - line.get_end_point().get_y())*self.height/full_height)
                if line.get_type() == "direct":
                    dxf_line = sdxf.Line(points=[(xstart,ystart,0),(xend,yend,0)])
                else:
                    dxf_line = sdxf.Line(points=[(xstart,ystart,0),(xend,yend,0)],color=1)
                d.append(dxf_line)
#        for circle in self.circle_array:
#            x = float(circle.get_m_Center().get_x()*self.width/full_width)
#            y = float((full_height - circle.get_m_Center().get_y())*self.height/full_height)
#            rad = circle.get_m_dRadius()
#            radius = rad*25/300
#            startAngle = (circle.get_start_angel())*180/math.pi
#            endAngle = (circle.get_end_angel())*180/math.pi
#            dxf_arc = sdxf.Arc(center=(x,y,0), radius=radius, startAngle=endAngle, endAngle=startAngle, color=5)
#            d.append(dxf_arc)
        return d
#---------------------------------------------------------------------------------------
#СЕГМЕНТАЦИЯ СКЕЛЕТА
#---------------------------------------------------------------------------------------
    #формирование массива сегментов
    def make_line_array(self, _point):
        _point.set_checked(True)
        usual = self.get_neighbor_usual(_point)
        if usual is not None:
            self.current_line = Line(_point)
            end_line = False
            while not end_line:
                self.current_line.append_to_point_array(usual)
                next = self.get_neighbor_usual(usual)
                if next is not None:
                    usual = next
                else:
                    end_line = True
            special = self.get_neighbor_special(usual)
            self.current_line.set_end_point(special)
            #проверяем что линия не совсем маленькая (>5 пикселей в длину)
            if len(self.current_line.get_point_array()) > 5:
                self.line_array.append(self.current_line)
                
    #поиск соседей среди обычных точек
    def get_neighbor_usual(self, _point):
        x,y = _point.get_x(),_point.get_y()
        point = None
        for a in xrange(x-1,x+2):
            for b in xrange(y-1,y+2):
                if self.skeleton_arr[a][b] == 1:
                    neighbors_count = self.neighbors_count(a, b)
                    point = Point(a, b, neighbors_count)
                    self.skeleton_arr[a][b] = 0
        return point
    
    #поиск соседей среди крайних точек                
    def get_neighbor_special(self, _point):
        x,y = _point.get_x(),_point.get_y()
        for point in self.special_point_array:
            if point.get_x() in (x-1, x, x+1) and point.get_y() in (y-1, y, y+1):
                point.set_checked(True)
                return point   

#---------------------------------------------------------------------------------------
#РАЗБИВКА СЕГМЕНТОВ НА СЕКЦИИ
#---------------------------------------------------------------------------------------            
    #разбиение каждого сегмента на секции
    def make_section_array(self, line):
        self.points_of_line = []
        self.current_line = line
        start_point = self.current_line.get_start_point()
        condition = True
        while condition:
            self.current_section = Section(start_point)
            current_point = self.do_5point_step(start_point)
            if current_point != self.current_line.get_end_point():
                #Вычисляем угол наклона прямой
                k = self.get_angle(start_point, current_point)
                #Двигаемся вперед пока угол между прямыми не станет больше 0.1 радиана (5.7 градуса)
                end_vector = False
                while not end_vector:
                    next_point = self.get_neighbor_in_line(current_point)
                    k1 = self.get_angle(start_point, next_point)
                    if abs(abs(k1) - abs(k)) < 0.03 and next_point != self.current_line.get_end_point():
                        current_point = next_point
                        self.current_line.get_point_array().remove(current_point)
                        self.points_of_line.append(current_point)
                        self.current_section.append_to_point_array(current_point)
                    else:
                        end_vector = True #Пора заканчивать вектор
                #Как только угол превысил норму переопределяем стартовую точку и сокраняем текущий вектор в массив     
                if next_point != self.current_line.get_end_point():
                    start_point = current_point   
                    self.current_section.set_end_point(current_point)
                    self.current_line.append_to_special_point_array(current_point)
                    self.current_line.append_to_section_array(self.current_section)
                else:
                    condition = False
            else:
                condition = False
        for point in self.points_of_line:
            self.current_line.append_to_point_array(point)
        self.current_section.set_end_point(self.current_line.get_end_point())
        self.current_line.append_to_special_point_array(self.current_line.get_end_point())
        self.current_line.append_to_section_array(self.current_section)
        
    #Метод выполняет 5 шагов для получения соседней точки
    def do_5point_step(self, _start_point):
        i = 1
        current_point = _start_point
        end = False
        while i < 6 and not end:
            next_point = self.get_neighbor_in_line(current_point)
            if next_point != self.current_line.get_end_point():
                current_point = next_point
                self.current_line.get_point_array().remove(current_point)
                self.points_of_line.append(current_point)
                self.current_section.append_to_point_array(current_point)
                i += 1
            else:
                current_point = self.current_line.get_end_point()
                end = True
        return current_point
                
                    
    def get_neighbor_in_line(self, _point):
        x,y = _point.get_x(),_point.get_y()
        for point in self.current_line.get_point_array():
            if point.get_x() in (x-1, x, x+1) and point.get_y() in (y-1, y, y+1) and not point.get_checked():
                return point
        return self.current_line.get_end_point()
    
    def get_neighbor_in_line1(self, _point):
        i = self.current_line.get_point_array().index(_point)
        return self.current_line.get_point_array()[i+1]